/* * Copyright 2001-2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jvnet.maven.jellydoc; import com.sun.javadoc.AnnotationDesc; import com.sun.javadoc.AnnotationDesc.ElementValuePair; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.Doc; import com.sun.javadoc.DocErrorReporter; import com.sun.javadoc.Doclet; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.PackageDoc; import com.sun.javadoc.Parameter; import com.sun.javadoc.ProgramElementDoc; import com.sun.javadoc.RootDoc; import com.sun.javadoc.SeeTag; import com.sun.javadoc.Tag; import com.sun.xml.txw2.TXW; import com.sun.xml.txw2.TypedXmlWriter; import com.sun.xml.txw2.output.StreamSerializer; import org.cyberneko.html.parsers.SAXParser; import org.jvnet.maven.jellydoc.annotation.NoContent; import org.jvnet.maven.jellydoc.annotation.Required; import org.jvnet.maven.jellydoc.annotation.TagLibUri; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import java.beans.Introspector; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.StringReader; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.Stack; /** * Main Doclet class to generate Tag Library ML. * * @author <a href="mailto:gopi@aztecsoft.com">Gopinath M.R.</a> * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> * @author Rodney Waldhoff */ // #### somehow we need to handle taglib inheritence... public class TagXMLDoclet extends Doclet { private String targetFileName = null; private String encodingFormat; public TagXMLDoclet (RootDoc root) throws Exception { readOptions(root); File targetFile = new File(targetFileName); targetFile.getParentFile().mkdirs(); FileOutputStream writer = new FileOutputStream(targetFileName); Tags tw = TXW.create(Tags.class,new StreamSerializer(writer)); javadocXML(root,tw); tw.commit(); } /** * Generates the xml for the tag libraries */ private void javadocXML(RootDoc root, Tags tw) throws SAXException { Set<PackageDoc> pkgs = new HashSet<PackageDoc>(); for (ClassDoc c : root.specifiedClasses()) pkgs.add(c.containingPackage()); pkgs.addAll(Arrays.asList(root.specifiedPackages())); // Generate for packages. for (PackageDoc pkg : pkgs) packageXML(pkg,tw); } /** * Generates doc for a tag library */ private void packageXML(PackageDoc packageDoc, Tags tw) throws SAXException { System.out.println( "processing package: " + packageDoc.name()); ClassDoc[] classArray = packageDoc.ordinaryClasses(); // lets see if we find a Tag boolean foundTag = false; for (ClassDoc classDoc : classArray) { if (isTag(classDoc)) { foundTag = true; break; } } if (!foundTag) return; Library library = tw.library(); library.name(packageDoc.name()); String name = packageDoc.name(); int idx = name.lastIndexOf('.'); if ( idx > 0 ) { name = name.substring(idx+1); } library.prefix(name); String uri = findUri(packageDoc.annotations()); if(uri==null) uri = "jelly:" + name; // fallback library.uri(uri); // generate Doc element. docXML(packageDoc,library); // generate tags for (ClassDoc c : classArray) { if (isTag(c)) { tagXML(c,library.tag()); } } } private String findUri(AnnotationDesc[] an) { for (AnnotationDesc a : an) if(a.annotationType().qualifiedName().equals(TagLibUri.class.getName())) for (ElementValuePair e : a.elementValues()) if(e.element().name().equals("value")) return e.value().value().toString(); return null; } private boolean has(ProgramElementDoc doc, Class<? extends Annotation> type) { for (AnnotationDesc a : doc.annotations()) if(a.annotationType().qualifiedName().equals(type.getName())) return true; return false; } /** * @return true if this class is a Jelly Tag */ private boolean isTag(ClassDoc classDoc) { ClassDoc[] interfaceArray = classDoc.interfaces(); for (ClassDoc i : interfaceArray) { String name = i.qualifiedName(); if ("org.apache.commons.jelly.Tag".equals(name)) { return true; } } ClassDoc base = classDoc.superclass(); return base != null && isTag(base); } /** * Generates doc for a tag */ private void tagXML(ClassDoc classDoc, org.jvnet.maven.jellydoc.Tag tag) throws SAXException { if (classDoc.isAbstract()) { return; } tag.className(classDoc.name()); String name = classDoc.name(); if ( name.endsWith( "Tag" ) ) { name = name.substring(0, name.length() - 3 ); } name = Introspector.decapitalize(name); System.out.println( "processing tag: " + name); tag.name(name); if(has(classDoc,NoContent.class)) tag.noContent(true); // generate "doc" sub-element docXML(classDoc,tag); // generate the attributes propertiesXML(classDoc,tag); } /** * Generates doc for a tag property */ private void propertiesXML(ClassDoc classDoc, org.jvnet.maven.jellydoc.Tag tag) throws SAXException { MethodDoc[] methodArray = classDoc.methods(); for (MethodDoc m : methodArray) { propertyXML(m,tag); } ClassDoc base = classDoc.superclass(); if ( base != null ) { propertiesXML( base, tag); } } /** * Generates doc for a tag property */ private void propertyXML(MethodDoc methodDoc, org.jvnet.maven.jellydoc.Tag tag) throws SAXException { if ( ! methodDoc.isPublic() || methodDoc.isStatic() ) { return; } String name = methodDoc.name(); if ( ! name.startsWith( "set" ) ) { return; } Parameter[] parameterArray = methodDoc.parameters(); if ( parameterArray == null || parameterArray.length != 1 ) { return; } Parameter parameter = parameterArray[0]; name = name.substring(3); name = Introspector.decapitalize(name); if ( name.equals( "body") || name.equals( "context" ) || name.equals( "parent" ) ) { return; } Attribute a = tag.attribute(); a.name(name); a.type(parameter.typeName()); if(has(methodDoc, Required.class)) a.use("required"); // maybe do more semantics, like use custom tags to denote if its required, optional etc. // generate "doc" sub-element docXML(methodDoc,a); } /** * Generates doc for element "doc" */ private void docXML(Doc doc, Item w) throws SAXException { TypedXmlWriter d = w.doc(); // handle the "comment" part, including {@link} tags { for (Tag tag : doc.inlineTags()) { // if tags[i] is an @link tag if (tag instanceof SeeTag) { String label = ((SeeTag) tag).label(); // if the label is null or empty, use the class#member part of the link if (null == label || "".equals(label)) { StringBuffer buf = new StringBuffer(); String className = ((SeeTag) tag).referencedClassName(); if ("".equals(className)) { className = null; } String memberName = ((SeeTag) tag).referencedMemberName(); if ("".equals(memberName)) { memberName = null; } if (null != className) { buf.append(className); if (null != memberName) { buf.append("."); } } if (null != memberName) { buf.append(memberName); } label = buf.toString(); } parseHTML(label,d); } else { parseHTML(tag.text(),d); } } } // handle the "tags" part for (Tag tag : doc.tags()) javadocTagXML(tag,w); } protected void parseHTML(String text, final TypedXmlWriter d) throws SAXException { SAXParser parser = new SAXParser(); parser.setProperty( "http://cyberneko.org/html/properties/names/elems", "lower" ); parser.setProperty( "http://cyberneko.org/html/properties/names/attrs", "lower" ); parser.setContentHandler( new DefaultHandler() { private Stack<TypedXmlWriter> w = new Stack<TypedXmlWriter>(); { w.push(d); } public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { if ( validDocElementName( localName ) ) { w.push(w.peek()._element(localName,TypedXmlWriter.class)); } } public void endElement(String namespaceURI, String localName, String qName) throws SAXException { if ( validDocElementName( localName ) ) { w.pop(); } } public void characters(char[] ch, int start, int length) throws SAXException { w.peek()._pcdata(new String(ch,start,length)); } } ); try { parser.parse( new InputSource(new StringReader( text )) ); } catch (IOException e) { System.err.println( "This should never happen!" + e ); } } /** * @return true if the given name is a valid HTML markup element. */ protected boolean validDocElementName(String name) { return ! name.equalsIgnoreCase( "html" ) && ! name.equalsIgnoreCase( "body" ); } /** * Generates doc for all tag elements. */ private void javadocTagXML(Tag tag, Item w) throws SAXException { String name = tag.name().substring(1) + "tag"; if (! tag.text().equals("")) w._element(name,TypedXmlWriter.class)._pcdata(tag.text()); } public static boolean start(RootDoc root) { try { new TagXMLDoclet(root); return true; } catch (Exception e) { e.printStackTrace(); System.exit(1); return false; } } private void readOptions(RootDoc root) { for (String[] opt : root.options()) { if (opt[0].equals("-d")) { targetFileName = opt[1] + "/taglib.xml"; } if (opt[0].equals("-encoding")) { encodingFormat = opt[1]; } } } public static int optionLength(String option) { if(option.equals("-d")) { return 2; } if(option.equals("-encoding")) { return 2; } return 0; } public static boolean validOptions(String options[][], DocErrorReporter reporter) { boolean foundEncodingOption = false; boolean foundDirOption = false; for (String[] opt : options) { if (opt[0].equals("-d")) { if (foundDirOption) { reporter.printError("Only one -d option allowed."); return false; } else { foundDirOption = true; } } if (opt[0].equals("-encoding")) { if (foundEncodingOption) { reporter.printError("Only one -encoding option allowed."); return false; } else { foundEncodingOption = true; } } } if (!foundDirOption) { reporter.printError("Usage: javadoc -d <directory> -doclet TagXMLDoclet ..."); return false; } return true; } }